/*
 * Copyright (c) 2024 Allwinner Device Co., Ltd.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/regulator/consumer.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/regmap.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>

#include <asm/io.h>
#include "osal_io.h"
#include "audio_control.h"
#include "audio_core.h"
#include "audio_driver_log.h"
#include "audio_platform_base.h"
#include "t527_aiao_impl.h"

#include "t527_codec_impl.h"

#define DRV_NAME    "sunxi-snd-codec"

#define HDF_LOG_TAG HDF_AUDIO_DRIVER

static void *g_regAcodecBase = NULL; // CODEC Reg Base Addr
static struct platform_device *g_codec_pdev;

struct sunxi_codec_clk {
	/* parent */
	struct clk *clk_pll_audio0_4x;
	struct clk *clk_pll_audio1_div2;
	struct clk *clk_pll_audio1_div5;
	/* module */
	struct clk *clk_audio_dac;
	struct clk *clk_audio_adc;
	/* bus & reset */
	struct clk *clk_bus;
	struct reset_control *clk_rst;
};

struct sunxi_codec_rglt {
    struct regulator *avcc;
    struct regulator *vdd;
    struct regulator *cpvin;
};

struct pacfg_level_trig {
	u32 pin;
	u32 msleep_0;
	u32 msleep_1;
	bool level;
};

struct sunxi_codec {
    struct platform_device *pdev;

    struct sunxi_codec_clk clk;
    struct sunxi_codec_rglt rglt;

    uint32_t pa_pin_max;
    struct pacfg_level_trig *level_trig;

    struct work_struct mic_en_work;
    struct AudioMixerControl *MicRegAttr;
};

static const uint32_t g_sample_rate_conv[] = {
    44100, 48000, 8000, 32000, 22050, 24000,
    16000, 11025, 12000, 192000, 96000,
};

static int snd_sunxi_clk_enable(struct sunxi_codec_clk *clk);
static void snd_sunxi_clk_disable(struct sunxi_codec_clk *clk);
static int snd_sunxi_clk_rate(struct sunxi_codec_clk *clk, enum AudioStreamType streamType, unsigned int freq);
static int snd_sunxi_pa_pin_enable(struct pacfg_level_trig *level_trig, uint32_t pa_pin_max);
static void snd_sunxi_pa_pin_disable(struct pacfg_level_trig *level_trig, u32 pa_pin_max);

/*******************************************************************************
 *  for adm api
 ******************************************************************************/
int32_t T527CodecAiaoGetCtrlOps(const struct AudioKcontrol *kcontrol, struct AudioCtrlElemValue *elemValue)
{
    uint32_t curValue;
    uint32_t rcurValue;
    void *codecVir = NULL;
    struct AudioMixerControl *mixerCtrl = NULL;
    if (kcontrol == NULL || kcontrol->privateValue <= 0 || elemValue == NULL) {
        AUDIO_DEVICE_LOG_ERR("Audio input param is NULL.");
        return HDF_ERR_INVALID_OBJECT;
    }
    mixerCtrl = (struct AudioMixerControl *)((volatile uintptr_t)kcontrol->privateValue);
    if (mixerCtrl == NULL) {
        AUDIO_DEVICE_LOG_ERR("mixerCtrl is NULL.");
        return HDF_FAILURE;
    }
    codecVir = OsalIoRemap(AIAO_REG_BASE, AIAO_MAX_REG_SIZE);
    if (codecVir == NULL) {
        AUDIO_DEVICE_LOG_ERR("codecVir is NULL.");
        return HDF_FAILURE;
    }
    curValue = OSAL_READL((void *)((uintptr_t)codecVir + mixerCtrl->reg));
    rcurValue = OSAL_READL((void *)((uintptr_t)codecVir + mixerCtrl->rreg));
    OsalIoUnmap(codecVir);

    if (AudioGetCtrlOpsReg(elemValue, mixerCtrl, curValue) != HDF_SUCCESS ||
        AudioGetCtrlOpsRReg(elemValue, mixerCtrl, rcurValue) != HDF_SUCCESS) {
        AUDIO_DEVICE_LOG_ERR("Audio codec get kcontrol reg and rreg failed.");
        return HDF_FAILURE;
    }

    return HDF_SUCCESS;
}

static void AudioUpdateCodecAiaoRegBits(const struct AudioMixerControl *mixerControl, uint32_t value)
{
    if (mixerControl == NULL) {
        AUDIO_DEVICE_LOG_ERR("param mixerControl is null.");
        return;
    }

    uint32_t curValue;
    void *codecVir = NULL;
    uint32_t mixerControlMask;

    value = value << mixerControl->shift;
    mixerControlMask = mixerControl->mask << mixerControl->shift;
    codecVir = OsalIoRemap(AIAO_REG_BASE, AIAO_MAX_REG_SIZE);
    if (codecVir == NULL) {
        AUDIO_DEVICE_LOG_ERR("codecVir is NULL.");
        return;
    }

    curValue = OSAL_READL((void *)((uintptr_t)codecVir + mixerControl->reg));
    curValue = (curValue & ~mixerControlMask) | (value & mixerControlMask);
    OSAL_WRITEL(curValue, (void *)((uintptr_t)codecVir + mixerControl->reg));
    OsalIoUnmap(codecVir);
}

int32_t T527CodecAiaoSetCtrlOps(const struct AudioKcontrol *kcontrol, const struct AudioCtrlElemValue *elemValue)
{
    uint32_t value;
    uint32_t rvalue;
    bool updateRReg = false;
    struct AudioMixerControl *mixerCtrl = NULL;
    if (kcontrol == NULL || (kcontrol->privateValue <= 0) || elemValue == NULL) {
        AUDIO_DEVICE_LOG_ERR("Audio input param is NULL.");
        return HDF_ERR_INVALID_OBJECT;
    }

    mixerCtrl = (struct AudioMixerControl *)((volatile uintptr_t)kcontrol->privateValue);
    if (AudioSetCtrlOpsReg(kcontrol, elemValue, mixerCtrl, &value) != HDF_SUCCESS) {
        AUDIO_DEVICE_LOG_ERR("AudioSetCtrlOpsReg is failed.");
        return HDF_ERR_INVALID_OBJECT;
    }
    AudioUpdateCodecAiaoRegBits(mixerCtrl, value);
    if (AudioSetCtrlOpsRReg(elemValue, mixerCtrl, &rvalue, &updateRReg) != HDF_SUCCESS) {
        AUDIO_DEVICE_LOG_ERR("AudioSetCtrlOpsRReg is failed.");
        return HDF_ERR_INVALID_OBJECT;
    }
    if (updateRReg) {
        AudioUpdateCodecAiaoRegBits(mixerCtrl, rvalue);
    }

    return HDF_SUCCESS;
}

// Read contrl reg bits value
int32_t T527CodecRegBitsRead(struct AudioMixerControl *regAttr, uint32_t *regValue)
{
    if (g_regAcodecBase == NULL || regAttr == NULL ||
        regAttr->reg < 0 || regValue == NULL) {
        AUDIO_DEVICE_LOG_ERR("input invalid parameter.");
        return HDF_ERR_INVALID_PARAM;
    }
    regAttr->value = SysReadl((uintptr_t)g_regAcodecBase + regAttr->reg);
    *regValue = regAttr->value;
    regAttr->value = (*regValue >> regAttr->shift) & regAttr->mask;
    if (regAttr->value > regAttr->max || regAttr->value < regAttr->min) {
        AUDIO_DEVICE_LOG_DEBUG("invalid bitsValue=0x%x", regAttr->value);
        return HDF_FAILURE;
    }
    if (regAttr->invert) {
        regAttr->value = regAttr->max - regAttr->value;
    }
    return HDF_SUCCESS;
}

// Update contrl reg bits value
int32_t T527CodecRegBitsUpdate(struct AudioMixerControl regAttr)
{
    int32_t ret;
    uint32_t newValue, newMask, oldValue;
    if (g_regAcodecBase == NULL || regAttr.reg < 0) {
        AUDIO_DEVICE_LOG_ERR("input invalid parameter.");
        return HDF_ERR_INVALID_PARAM;
    }
    if (regAttr.invert) {
        regAttr.value = regAttr.max - regAttr.value;
    }
    newValue = regAttr.value << regAttr.shift;
    newMask = regAttr.mask << regAttr.shift;
    ret = T527CodecRegBitsRead(&regAttr, &oldValue);
    if (ret != HDF_SUCCESS) {
        ADM_LOG_ERR("T527CodecRegBitsRead failed, ret=%d.", ret);
        return HDF_FAILURE;
    }
    regAttr.value = (oldValue & ~newMask) | (newValue & newMask);
    SysWritel((uintptr_t)g_regAcodecBase + regAttr.reg, regAttr.value);
    return HDF_SUCCESS;
}

uint32_t T527CodecImplRegmapRead(uint32_t reg, uint32_t *value)
{
    if (reg > AIAO_MAX_REG_SIZE) {
	AUDIO_DEVICE_LOG_ERR("reg addr beyond max addr!");
	return HDF_ERR_INVALID_PARAM;
    }

    *value = SysReadl((uintptr_t)g_regAcodecBase + reg);

    return HDF_SUCCESS;
}

int32_t T527CodecImplRegmapWrite(uint32_t reg, uint32_t value)
{
    if (reg > AIAO_MAX_REG_SIZE) {
	AUDIO_DEVICE_LOG_ERR("reg addr beyond max addr!");
	return HDF_ERR_INVALID_PARAM;
    }

    SysWritel((uintptr_t)g_regAcodecBase + reg, value);

    return HDF_SUCCESS;
}

int32_t T527CodecHalSysInit(struct CodecData *codeData)
{
    if (codeData == NULL) {
        AUDIO_DEVICE_LOG_ERR("input invalid parameter.");
        return HDF_ERR_INVALID_PARAM;
    }
    // ACODEC REMAP
    if (g_regAcodecBase == NULL) {
        g_regAcodecBase = (void *)(uintptr_t)(codeData->virtualAddress);
        if (g_regAcodecBase == NULL) {
            AUDIO_DEVICE_LOG_ERR("regAcodecBase is null.");
            return HDF_FAILURE;
        }
    }
    return HDF_SUCCESS;
}

int32_t T527CodecRegDefaultInit(struct AudioRegCfgGroupNode **regCfgGroup)
{
    int32_t i;
    struct AudioAddrConfig *regAttr = NULL;
    if (g_regAcodecBase == NULL || regCfgGroup == NULL || regCfgGroup[AUDIO_INIT_GROUP] == NULL ||
        regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem == NULL || regCfgGroup[AUDIO_INIT_GROUP]->itemNum <= 0) {
        AUDIO_DEVICE_LOG_ERR("input invalid parameter.");
        return HDF_FAILURE;
    }
    regAttr = regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem;

    for (i = 0; i < regCfgGroup[AUDIO_INIT_GROUP]->itemNum; i++) {
        SysWritel((uintptr_t)g_regAcodecBase + regAttr[i].addr, regAttr[i].value);
    }
    AUDIO_DEVICE_LOG_DEBUG("success.");
    return HDF_SUCCESS;
}

int32_t T527CodecImplHwParams(struct AudioRegCfgGroupNode **regCfgGroup, enum AudioStreamType streamType, 
            enum AudioFormat format, uint32_t channels, uint32_t rate)
{
    int i;
    uint32_t freq_point;
    int32_t ret;
    /*const int itemNum = 42;*/
    struct AudioMixerControl *regAttr = NULL;
    struct sunxi_codec *codec = dev_get_drvdata(&g_codec_pdev->dev);
    struct sunxi_codec_clk *clk = &codec->clk;
    /*struct regmap *regmap = codec->mem.regmap;*/

    AUDIO_DRIVER_LOG_DEBUG("");

    ret = (regCfgGroup == NULL
        || regCfgGroup[AUDIO_DAI_PATAM_GROUP] == NULL
        || regCfgGroup[AUDIO_DAI_PATAM_GROUP]->regCfgItem == NULL);
    if (ret) {
        AUDIO_DEVICE_LOG_ERR("input invalid parameter.");
        return HDF_FAILURE;
    }
    regAttr = regCfgGroup[AUDIO_DAI_PATAM_GROUP]->regCfgItem;

    /* set pll clk */
    switch (rate) {
        case 8000:
        case 12000:
        case 16000:
        case 24000:
        case 32000:
        case 48000:
        case 64000:
        case 96000:
        case 192000:
            freq_point = 24576000;
            break;
        case 11025:
        case 22050:
        case 44100:
        case 88200:
        case 176400:
            freq_point = 22579200;
            break;
        default:
            AUDIO_DRIVER_LOG_ERR("rate: %d is not define.", rate);
            return HDF_FAILURE;
    }
    AUDIO_DRIVER_LOG_DEBUG(" freq_point %u", freq_point);

    /* set clk rate */
    if (snd_sunxi_clk_rate(clk, streamType, freq_point)) {
        AUDIO_DRIVER_LOG_ERR("module clk set rate failed");
        return -HDF_FAILURE;
    }
    if (snd_sunxi_clk_enable(clk)) {
        AUDIO_DRIVER_LOG_ERR("module clk set enable failed");
        return -HDF_FAILURE;
    }

    /* set bits */
    switch (format) {
        case AUDIO_FORMAT_TYPE_PCM_16_BIT:
            if (streamType == AUDIO_RENDER_STREAM) {
                T527CodecRegBitsUpdate(regAttr[0]);
                T527CodecRegBitsUpdate(regAttr[1]);
            } else {
                T527CodecRegBitsUpdate(regAttr[2]);
                T527CodecRegBitsUpdate(regAttr[3]);
            }
            AUDIO_DRIVER_LOG_DEBUG(" format 16");
            break;
        case AUDIO_FORMAT_TYPE_PCM_32_BIT:
        case AUDIO_FORMAT_TYPE_PCM_24_BIT:
            if (streamType == AUDIO_RENDER_STREAM) {
                T527CodecRegBitsUpdate(regAttr[4]);
                T527CodecRegBitsUpdate(regAttr[5]);
            } else {
                T527CodecRegBitsUpdate(regAttr[6]);
                T527CodecRegBitsUpdate(regAttr[7]);
            }
            AUDIO_DRIVER_LOG_DEBUG(" format 24");
            break;
        default:
            AUDIO_DRIVER_LOG_ERR("format: %d is not define.", format);
            return HDF_FAILURE;
    }

    /* set rate */
    if (streamType == AUDIO_RENDER_STREAM) {
        for (i = 0; i < ARRAY_SIZE(g_sample_rate_conv); i++) {
            if (g_sample_rate_conv[i] == rate)
                T527CodecRegBitsUpdate(regAttr[8 + i]);
        }
    } else {
        for (i = 0; i < ARRAY_SIZE(g_sample_rate_conv); i++) {
            if (g_sample_rate_conv[i] == rate) {
                if (g_sample_rate_conv[i] > 48000)
                    return -EINVAL;
                T527CodecRegBitsUpdate(regAttr[19 + i]);
            }
        }
    }
    AUDIO_DRIVER_LOG_DEBUG(" rate %u", rate);

    /* set channels */
	if (channels == 1) {
		if (streamType == AUDIO_RENDER_STREAM)
            T527CodecRegBitsUpdate(regAttr[30]);
        else
            return -HDF_FAILURE;
	} else {
		if (streamType == AUDIO_RENDER_STREAM)
            T527CodecRegBitsUpdate(regAttr[31]);
	}
    AUDIO_DRIVER_LOG_DEBUG(" channels %u", channels);

    /* clear fifo */
    if (streamType == AUDIO_RENDER_STREAM) {
        T527CodecRegBitsUpdate(regAttr[32]);
        T527CodecRegBitsUpdate(regAttr[33]);
        T527CodecRegBitsUpdate(regAttr[34]);
    } else {
        T527CodecRegBitsUpdate(regAttr[35]);
        T527CodecRegBitsUpdate(regAttr[36]);
        T527CodecRegBitsUpdate(regAttr[37]);
    }

    AUDIO_DRIVER_LOG_DEBUG("success.");
    return HDF_SUCCESS;
}

static void renderRouteCtrl(struct AudioMixerControl *regAttr, bool enable)
{
    AUDIO_DRIVER_LOG_DEBUG("");

    if (enable) {
        T527CodecRegBitsUpdate(regAttr[42]);
        T527CodecRegBitsUpdate(regAttr[43]);
    } else {
        T527CodecRegBitsUpdate(regAttr[44]);
        T527CodecRegBitsUpdate(regAttr[45]);
    }
}

int32_t T527CodecImplTrigger(struct AudioRegCfgGroupNode **regCfgGroup,
                             enum AudioStreamType streamType, bool enable)
{
    struct sunxi_codec *codec = dev_get_drvdata(&g_codec_pdev->dev);
    struct AudioMixerControl *regAttr = NULL;
    int32_t ret;

    AUDIO_DRIVER_LOG_DEBUG("");

    ret = (regCfgGroup == NULL
        || regCfgGroup[AUDIO_DAI_PATAM_GROUP] == NULL
        || regCfgGroup[AUDIO_DAI_PATAM_GROUP]->regCfgItem == NULL);
    if (ret) {
        AUDIO_DEVICE_LOG_ERR("input invalid parameter.");
        return HDF_FAILURE;
    }
    regAttr = regCfgGroup[AUDIO_DAI_PATAM_GROUP]->regCfgItem;

    if (streamType == AUDIO_RENDER_STREAM) {
        renderRouteCtrl(regAttr, enable);
        if (enable) {
            T527CodecRegBitsUpdate(regAttr[38]);
            snd_sunxi_pa_pin_enable(codec->level_trig, codec->pa_pin_max);
        } else {
            snd_sunxi_pa_pin_disable(codec->level_trig, codec->pa_pin_max);
            T527CodecRegBitsUpdate(regAttr[39]);
        }
    } else {
        if (enable) {
	    codec->MicRegAttr = regAttr;
	    schedule_work(&codec->mic_en_work);
        } else {
            T527CodecRegBitsUpdate(regAttr[50]);
            T527CodecRegBitsUpdate(regAttr[51]);
            T527CodecRegBitsUpdate(regAttr[52]);
            T527CodecRegBitsUpdate(regAttr[53]);
            T527CodecRegBitsUpdate(regAttr[41]);
        }
    }

    AUDIO_DRIVER_LOG_DEBUG("success.");
    return HDF_SUCCESS;
}
int32_t T527CodecImplShutdown()
{
    struct sunxi_codec *codec = dev_get_drvdata(&g_codec_pdev->dev);
    struct sunxi_codec_clk *clk = &codec->clk;

    AUDIO_DRIVER_LOG_DEBUG("");

    snd_sunxi_clk_disable(clk);

    AUDIO_DRIVER_LOG_DEBUG("success.");
    return HDF_SUCCESS;
}
/*******************************************************************************
 * for linux probe
*******************************************************************************/
static int snd_sunxi_clk_init(struct platform_device *pdev, struct sunxi_codec_clk *clk);
static void snd_sunxi_clk_exit(struct sunxi_codec_clk *clk);
static int snd_sunxi_clk_bus_enable(struct sunxi_codec_clk *clk);
static void snd_sunxi_clk_bus_disable(struct sunxi_codec_clk *clk);

static int snd_sunxi_rglt_init(struct platform_device *pdev, struct sunxi_codec_rglt *rglt);
static void snd_sunxi_rglt_exit(struct sunxi_codec_rglt *rglt);

static int snd_sunxi_clk_init(struct platform_device *pdev, struct sunxi_codec_clk *clk)
{
    int ret = 0;
    struct device_node *np = pdev->dev.of_node;

    AUDIO_DRIVER_LOG_DEBUG("");

    /* get rst clk */
    clk->clk_rst = devm_reset_control_get(&pdev->dev, NULL);
    if (IS_ERR_OR_NULL(clk->clk_rst)) {
        AUDIO_DRIVER_LOG_ERR("clk rst get failed\n");
        ret =  PTR_ERR(clk->clk_rst);
        goto err_get_clk_rst;
    }

    /* get bus clk */
    clk->clk_bus = of_clk_get_by_name(np, "clk_bus_audio");
    if (IS_ERR_OR_NULL(clk->clk_bus)) {
        AUDIO_DRIVER_LOG_ERR("clk bus get failed\n");
        ret = PTR_ERR(clk->clk_bus);
        goto err_get_clk_bus;
    }

    /* get parent clk */
    clk->clk_pll_audio0_4x = of_clk_get_by_name(np, "clk_pll_audio0_4x");
    if (IS_ERR_OR_NULL(clk->clk_pll_audio0_4x)) {
        AUDIO_DRIVER_LOG_ERR("clk_pll_audio0_4x get failed");
        ret = PTR_ERR(clk->clk_pll_audio0_4x);
        goto err_get_clk_pll_audio0_4x;
    }

    clk->clk_pll_audio1_div2 = of_clk_get_by_name(np, "clk_pll_audio1_div2");
    if (IS_ERR_OR_NULL(clk->clk_pll_audio1_div2)) {
        AUDIO_DRIVER_LOG_ERR("clk_pll_audio1_div2 get failed");
        ret = PTR_ERR(clk->clk_pll_audio1_div2);
        goto err_get_clk_pll_audio1_div2;
    }

    clk->clk_pll_audio1_div5 = of_clk_get_by_name(np, "clk_pll_audio1_div5");
    if (IS_ERR_OR_NULL(clk->clk_pll_audio1_div5)) {
        AUDIO_DRIVER_LOG_ERR("clk_pll_audio1_div5 get failed");
        ret = PTR_ERR(clk->clk_pll_audio1_div5);
        goto err_get_clk_pll_audio1_div5;
    }

    /* get module clk */
    clk->clk_audio_dac = of_clk_get_by_name(np, "clk_audio_dac");
    if (IS_ERR_OR_NULL(clk->clk_audio_dac)) {
        AUDIO_DRIVER_LOG_ERR("clk_audio_dac get failed");
        ret = PTR_ERR(clk->clk_audio_dac);
        goto err_get_clk_audio_dac;
    }

    clk->clk_audio_adc = of_clk_get_by_name(np, "clk_audio_adc");
    if (IS_ERR_OR_NULL(clk->clk_audio_adc)) {
        AUDIO_DRIVER_LOG_ERR("clk_audio_adc get failed");
        ret = PTR_ERR(clk->clk_audio_adc);
        goto err_get_clk_audio_adc;
    }

    ret = snd_sunxi_clk_bus_enable(clk);
    if (ret) {
        AUDIO_DRIVER_LOG_ERR("bus clk enable failed");
        ret = -EINVAL;
        goto err_bus_clk_enable;
    }

    return 0;

err_bus_clk_enable:
    clk_put(clk->clk_audio_adc);
err_get_clk_audio_adc:
    clk_put(clk->clk_audio_dac);
err_get_clk_audio_dac:
    clk_put(clk->clk_pll_audio1_div5);
err_get_clk_pll_audio1_div5:
    clk_put(clk->clk_pll_audio1_div2);
err_get_clk_pll_audio1_div2:
    clk_put(clk->clk_pll_audio0_4x);
err_get_clk_pll_audio0_4x:
    clk_put(clk->clk_bus);
err_get_clk_bus:
err_get_clk_rst:
    return ret;
}

static void snd_sunxi_clk_exit(struct sunxi_codec_clk *clk)
{
    AUDIO_DRIVER_LOG_DEBUG("");

    snd_sunxi_clk_bus_disable(clk);
    clk_put(clk->clk_audio_adc);
    clk_put(clk->clk_audio_dac);
    clk_put(clk->clk_pll_audio1_div5);
    clk_put(clk->clk_pll_audio1_div2);
    clk_put(clk->clk_pll_audio0_4x);
    clk_put(clk->clk_bus);
}

static int snd_sunxi_clk_bus_enable(struct sunxi_codec_clk *clk)
{
    int ret = 0;

    AUDIO_DRIVER_LOG_DEBUG("");

    if (reset_control_deassert(clk->clk_rst)) {
        AUDIO_DRIVER_LOG_ERR("clk rst deassert failed\n");
        ret = -EINVAL;
        goto err_deassert_rst;
    }

    if (clk_prepare_enable(clk->clk_bus)) {
        AUDIO_DRIVER_LOG_ERR("clk bus enable failed\n");
        goto err_enable_clk_bus;
    }

    return 0;

err_enable_clk_bus:
    reset_control_assert(clk->clk_rst);
err_deassert_rst:
    return ret;
}

static void snd_sunxi_clk_bus_disable(struct sunxi_codec_clk *clk)
{
    AUDIO_DRIVER_LOG_DEBUG("");

    clk_disable_unprepare(clk->clk_bus);
    reset_control_assert(clk->clk_rst);
}

static int snd_sunxi_clk_enable(struct sunxi_codec_clk *clk)
{
	int ret = 0;

	AUDIO_DRIVER_LOG_DEBUG("\n");

	if (clk_prepare_enable(clk->clk_pll_audio0_4x)) {
		AUDIO_DRIVER_LOG_ERR("clk_pll_audio0_4x enable failed\n");
		ret = -EINVAL;
		goto err_enable_clk_pll_audio0_4x;
	}
	if (clk_prepare_enable(clk->clk_pll_audio1_div2)) {
		AUDIO_DRIVER_LOG_ERR("clk_pll_audio1_div2 enable failed\n");
		ret = -EINVAL;
		goto err_enable_clk_pll_audio1_div2;
	}

	if (clk_prepare_enable(clk->clk_pll_audio1_div5)) {
		AUDIO_DRIVER_LOG_ERR("clk_pll_audio1_div5 enable failed\n");
		ret = -EINVAL;
		goto err_enable_clk_pll_audio1_div5;
	}

	if (clk_prepare_enable(clk->clk_audio_dac)) {
		AUDIO_DRIVER_LOG_ERR("clk_audio_dac enable failed\n");
		ret = -EINVAL;
		goto err_enable_clk_audio_dac;
	}

	if (clk_prepare_enable(clk->clk_audio_adc)) {
		AUDIO_DRIVER_LOG_ERR("clk_audio_adc enable failed\n");
		ret = -EINVAL;
		goto err_enable_clk_audio_adc;
	}

	return 0;

err_enable_clk_audio_adc:
	clk_disable_unprepare(clk->clk_audio_dac);
err_enable_clk_audio_dac:
	clk_disable_unprepare(clk->clk_pll_audio1_div5);
err_enable_clk_pll_audio1_div5:
	clk_disable_unprepare(clk->clk_pll_audio1_div2);
err_enable_clk_pll_audio1_div2:
	clk_disable_unprepare(clk->clk_pll_audio0_4x);
err_enable_clk_pll_audio0_4x:
	return ret;
}

static void snd_sunxi_clk_disable(struct sunxi_codec_clk *clk)
{
    AUDIO_DRIVER_LOG_DEBUG("\n");

	clk_disable_unprepare(clk->clk_audio_adc);
	clk_disable_unprepare(clk->clk_audio_dac);
	clk_disable_unprepare(clk->clk_pll_audio1_div5);
	clk_disable_unprepare(clk->clk_pll_audio1_div2);
	clk_disable_unprepare(clk->clk_pll_audio0_4x);
}

static int snd_sunxi_clk_rate(struct sunxi_codec_clk *clk, enum AudioStreamType streamType, unsigned int freq)
{
	AUDIO_DRIVER_LOG_DEBUG("\n");

	if (streamType  == AUDIO_RENDER_STREAM) {
		if (freq % 24576000 == 0) {
			/* If you want to use clk_pll_audio0_4x, must set it 196608000Hz */
			if (clk_set_parent(clk->clk_audio_dac, clk->clk_pll_audio0_4x)) {
				AUDIO_DRIVER_LOG_ERR("set dac parent clk failed\n");
				return -EINVAL;
			}
			if (clk_set_rate(clk->clk_pll_audio0_4x, 196608000)) {
				AUDIO_DRIVER_LOG_ERR("set clk_pll_audio0_4x rate failed\n");
				return -EINVAL;
			}
		} else {
			if (clk_set_parent(clk->clk_audio_dac, clk->clk_pll_audio1_div5)) {
				AUDIO_DRIVER_LOG_ERR("set dac parent clk failed\n");
				return -EINVAL;
			}
		}
		if (clk_set_rate(clk->clk_audio_dac, freq)) {
			AUDIO_DRIVER_LOG_ERR("set clk_audio_dac rate failed, rate: %u\n", freq);
			return -EINVAL;
		}
	} else {
		if (freq % 24576000 == 0) {
			/* If you want to use clk_pll_audio0_4x, must set it 196608000Hz */
			if (clk_set_parent(clk->clk_audio_adc, clk->clk_pll_audio0_4x)) {
				AUDIO_DRIVER_LOG_ERR("set adc parent clk failed\n");
				return -EINVAL;
			}
			if (clk_set_rate(clk->clk_pll_audio0_4x, 196608000)) {
				AUDIO_DRIVER_LOG_ERR("set clk_pll_audio0_4x rate failed\n");
				return -EINVAL;
			}
		} else {
			if (clk_set_parent(clk->clk_audio_adc, clk->clk_pll_audio1_div5)) {
				AUDIO_DRIVER_LOG_ERR("set adc parent clk failed\n");
				return -EINVAL;
			}
		}
		if (clk_set_rate(clk->clk_audio_adc, freq)) {
			AUDIO_DRIVER_LOG_ERR("set clk_audio_adc rate failed, rate: %u\n", freq);
			return -EINVAL;
		}
	}

	return 0;
}

static int snd_sunxi_rglt_init(struct platform_device *pdev, struct sunxi_codec_rglt *rglt)
{
    int ret = 0;

    AUDIO_DRIVER_LOG_DEBUG("");

    /* avcc regulator */
    rglt->avcc = regulator_get(&pdev->dev, "avcc");
    if (IS_ERR_OR_NULL(rglt->avcc)) {
        AUDIO_DRIVER_LOG_ERR("get avcc failed");
        ret = -EFAULT;
        goto err_regulator_get_avcc;
    }
    ret = regulator_set_voltage(rglt->avcc, 1800000, 1800000);
    if (ret < 0) {
        AUDIO_DRIVER_LOG_ERR("set avcc voltage failed");
        ret = -EFAULT;
        goto err_regulator_set_vol_avcc;
    }
    ret = regulator_enable(rglt->avcc);
    if (ret < 0) {
        AUDIO_DRIVER_LOG_ERR("enable avcc failed");
        ret = -EFAULT;
        goto err_regulator_enable_avcc;
    }

    /* vdd regulator */
    rglt->vdd = regulator_get(&pdev->dev, "vdd");
    if (IS_ERR_OR_NULL(rglt->vdd)) {
        AUDIO_DRIVER_LOG_ERR("get vdd failed");
        ret = -EFAULT;
        goto err_regulator_get_vdd;
    }
    ret = regulator_set_voltage(rglt->vdd, 3300000, 3300000);
    if (ret < 0) {
        AUDIO_DRIVER_LOG_ERR("set vdd voltage failed");
        ret = -EFAULT;
        goto err_regulator_set_vol_vdd;
    }
    ret = regulator_enable(rglt->vdd);
    if (ret < 0) {
        AUDIO_DRIVER_LOG_ERR("enable vdd failed");
        ret = -EFAULT;
        goto err_regulator_enable_vdd;
    }

    /* cpvin regulator */
    rglt->cpvin = regulator_get(&pdev->dev, "cpvin");
    if (IS_ERR_OR_NULL(rglt->cpvin)) {
        AUDIO_DRIVER_LOG_ERR("get cpvin failed");
        ret = -EFAULT;
        goto err_regulator_get_cpvin;
    }
    ret = regulator_set_voltage(rglt->cpvin, 1800000, 1800000);
    if (ret < 0) {
        AUDIO_DRIVER_LOG_ERR("set cpvin voltage failed");
        ret = -EFAULT;
        goto err_regulator_set_vol_cpvin;
    }
    ret = regulator_enable(rglt->cpvin);
    if (ret < 0) {
        AUDIO_DRIVER_LOG_ERR("enable cpvin failed");
        ret = -EFAULT;
        goto err_regulator_enable_cpvin;
    }

    return 0;

err_regulator_enable_cpvin:
err_regulator_set_vol_cpvin:
    regulator_put(rglt->cpvin);
err_regulator_get_cpvin:
    regulator_disable(rglt->vdd);
err_regulator_enable_vdd:
err_regulator_set_vol_vdd:
    regulator_put(rglt->vdd);
err_regulator_get_vdd:
    regulator_disable(rglt->avcc);
err_regulator_enable_avcc:
err_regulator_set_vol_avcc:
    regulator_put(rglt->avcc);
err_regulator_get_avcc:
    return ret;
}

static void snd_sunxi_rglt_exit(struct sunxi_codec_rglt *rglt)
{
    AUDIO_DRIVER_LOG_DEBUG("");

    if (!IS_ERR_OR_NULL(rglt->avcc)) {
        regulator_disable(rglt->avcc);
        regulator_put(rglt->avcc);
    }

    if (!IS_ERR_OR_NULL(rglt->avcc)) {
        regulator_disable(rglt->avcc);
        regulator_put(rglt->avcc);
    }

    if (!IS_ERR_OR_NULL(rglt->avcc)) {
        regulator_disable(rglt->avcc);
        regulator_put(rglt->avcc);
    }
}

static int snd_sunxi_pa_pin_enable(struct pacfg_level_trig *level_trig, uint32_t pa_pin_max)
{
    int32_t i;

    AUDIO_DRIVER_LOG_DEBUG("\n");

	if (pa_pin_max < 1) {
		AUDIO_DRIVER_LOG_DEBUG("no pa pin config\n");
		return 0;
	}

    for (i = 0; i < pa_pin_max; i++) {
        if (level_trig[i].msleep_0)
            msleep(level_trig[i].msleep_0);
        gpio_set_value(level_trig[i].pin, level_trig[i].level);
	}

    return 0;
}

static void snd_sunxi_pa_pin_disable(struct pacfg_level_trig *level_trig, uint32_t pa_pin_max)
{

	int32_t i;

	AUDIO_DRIVER_LOG_DEBUG("\n");

	if (pa_pin_max < 1) {
		AUDIO_DRIVER_LOG_DEBUG("no pa pin config\n");
		return;
	}

    for (i = 0; i < pa_pin_max; i++) {
        gpio_set_value(level_trig[i].pin, !level_trig[i].level);
        if (level_trig[i].msleep_1)
			msleep(level_trig[i].msleep_1);
    }
}

static struct pacfg_level_trig *snd_sunxi_pa_pin_init(struct platform_device *pdev, uint32_t *pa_pin_max)
{
    uint32_t pin_max, temp_val;
    uint32_t gpio_tmp;
    char str[32] = {0};
    struct pacfg_level_trig *level_trig;
    struct device_node *np = pdev->dev.of_node;
    int32_t ret, i;

    AUDIO_DRIVER_LOG_DEBUG("");

	*pa_pin_max = 0;
	ret = of_property_read_u32(np, "pa-pin-max", &temp_val);
	if (ret < 0) {
		AUDIO_DRIVER_LOG_DEBUG("pa-pin-max get failed, default 0\n");
		return NULL;
	} else {
		pin_max = temp_val;
	}

	level_trig = kzalloc(sizeof(*level_trig) * pin_max, GFP_KERNEL);
	if (!level_trig) {
		AUDIO_DRIVER_LOG_ERR("can't pacfg_level_trig memory\n");
		return NULL;
	}

    for (i = 0; i < pin_max; i++) {
        snprintf(str, sizeof(str), "pa-pin-%d", i);
        ret = of_get_named_gpio(np, str, 0);
        if (ret < 0) {
            AUDIO_DRIVER_LOG_ERR("%s get failed\n", str);
            continue;
        }
        gpio_tmp = ret;
        if (!gpio_is_valid(gpio_tmp)) {
            AUDIO_DRIVER_LOG_ERR("%s (%u) is invalid\n", str, gpio_tmp);
            continue;
        }
        ret = devm_gpio_request(&pdev->dev, gpio_tmp, str);
        if (ret) {
            AUDIO_DRIVER_LOG_ERR("%s (%u) request failed\n", str, gpio_tmp);
            continue;
        }
        level_trig[i].pin = gpio_tmp;

        snprintf(str, sizeof(str), "pa-pin-level-%d", i);
        ret = of_property_read_u32(np, str, &temp_val);
        if (ret < 0) {
            AUDIO_DRIVER_LOG_WARNING("%s get failed, default low\n", str);
            level_trig[i].level = 0;
        } else {
            if (temp_val > 0)
                level_trig[i].level = 1;
        }
        snprintf(str, sizeof(str), "pa-pin-msleep-%d", i);
        ret = of_property_read_u32(np, str, &temp_val);
        if (ret < 0) {
            AUDIO_DRIVER_LOG_WARNING("%s get failed, default 0\n", str);
            level_trig[i].msleep_0 = 0;
        } else {
            level_trig[i].msleep_0 = temp_val;
        }
        snprintf(str, sizeof(str), "pa-pin-msleep1-%d", i);
        ret = of_property_read_u32(np, str, &temp_val);
        if (ret < 0) {
            AUDIO_DRIVER_LOG_WARNING("%s get failed, default 0\n", str);
            level_trig[i].msleep_1 = 0;
        } else {
            level_trig[i].msleep_1 = temp_val;
        }
        gpio_direction_output(level_trig[i].pin, !level_trig[i].level);
    }

    *pa_pin_max = pin_max;
    snd_sunxi_pa_pin_disable(level_trig, pin_max);
    return level_trig;
}

static void snd_sunxi_pa_pin_exit(struct pacfg_level_trig *level_trig, uint32_t pa_pin_max)
{
    AUDIO_DRIVER_LOG_DEBUG("");

    snd_sunxi_pa_pin_disable(level_trig, pa_pin_max);

    kfree(level_trig);
}

static void mic_switch_enable(struct work_struct *work)
{
    struct sunxi_codec *codec = container_of(work, struct sunxi_codec, mic_en_work);
    struct AudioMixerControl *regAttr = codec->MicRegAttr;

    T527CodecRegBitsUpdate(regAttr[46]);
    T527CodecRegBitsUpdate(regAttr[47]);
    T527CodecRegBitsUpdate(regAttr[48]);
    msleep(240);
    T527CodecRegBitsUpdate(regAttr[49]);
    T527CodecRegBitsUpdate(regAttr[40]);
}

static int sunxi_internal_codec_dev_probe(struct platform_device *pdev)
{
    int ret;
    struct device *dev = &pdev->dev;
    struct device_node *np = pdev->dev.of_node;
    struct sunxi_codec *codec;
    struct sunxi_codec_clk *clk;
    struct sunxi_codec_rglt *rglt;

    AUDIO_DRIVER_LOG_DEBUG("");

    /* sunxi codec info */
    codec = devm_kzalloc(dev, sizeof(struct sunxi_codec), GFP_KERNEL);
    if (!codec) {
        AUDIO_DRIVER_LOG_ERR("can't allocate sunxi codec memory");
        ret = -ENOMEM;
        goto err_devm_kzalloc;
    }
    dev_set_drvdata(dev, codec);
    clk = &codec->clk;
    rglt = &codec->rglt;

    /* clk init */
    ret = snd_sunxi_clk_init(pdev, clk);
    if (ret) {
        AUDIO_DRIVER_LOG_ERR("clk init failed");
        ret = -ENOMEM;
        goto err_clk_init;
    }

    /* regulator init */
    ret = snd_sunxi_rglt_init(pdev, rglt);
    if (ret) {
        AUDIO_DRIVER_LOG_ERR("regulator init failed");
        ret = -ENOMEM;
        goto err_regulator_init;
    }

    /* pa_pin init */
    codec->level_trig = snd_sunxi_pa_pin_init(pdev, &codec->pa_pin_max);

    INIT_WORK(&codec->mic_en_work, mic_switch_enable);

    g_codec_pdev = pdev;

    AUDIO_DRIVER_LOG_DEBUG("register internal-codec codec success");

    return 0;

err_regulator_init:
    snd_sunxi_clk_exit(clk);
err_clk_init:
    devm_kfree(dev, codec);
err_devm_kzalloc:
    of_node_put(np);

    return ret;
}

static int sunxi_internal_codec_dev_remove(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct sunxi_codec *codec = dev_get_drvdata(dev);
    struct sunxi_codec_clk *clk = &codec->clk;
    struct sunxi_codec_rglt *rglt = &codec->rglt;

    AUDIO_DRIVER_LOG_DEBUG("");

    snd_sunxi_clk_exit(clk);
    snd_sunxi_rglt_exit(rglt);
    snd_sunxi_pa_pin_exit(codec->level_trig, codec->pa_pin_max);
    cancel_work_sync(&codec->mic_en_work);

    /* sunxi codec custom info free */
    devm_kfree(dev, codec);
    of_node_put(pdev->dev.of_node);

    AUDIO_DRIVER_LOG_DEBUG("unregister internal-codec codec success");

    return 0;
}

static const struct of_device_id sunxi_internal_codec_of_match[] = {
    { .compatible = "allwinner," DRV_NAME, },
    {},
};
MODULE_DEVICE_TABLE(of, sunxi_internal_codec_of_match);

static struct platform_driver sunxi_internal_codec_driver = {
    .driver = {
        .name   = DRV_NAME,
        .owner  = THIS_MODULE,
        .of_match_table = sunxi_internal_codec_of_match,
    },
    .probe  = sunxi_internal_codec_dev_probe,
    .remove = sunxi_internal_codec_dev_remove,
};

module_platform_driver(sunxi_internal_codec_driver);

MODULE_DESCRIPTION("sunxi soundcard codec of internal-codec");
